📌 Trabajo Final de Diplomatura en Inteligencia Artificial - Grupo 6 - Cohorte 2024
Sistema para la clasificación correcta de residuos.¶
Integrantes:¶
Valentina Botta
Gustavo Lucarella
Mauricio Magistocchi
Edgardo Eliseo Cornelio
🟢 "En este cuaderno, clasificaremos los desechos (trash) o no, utilizando una Red Neuronal Convolucional (CNN).".
Tabla de contenidos¶
import os
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import cv2
import tensorflow as tf
import random
import shutil # to copy images to another directory
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report , confusion_matrix
from tqdm import tqdm
from keras.layers import Conv2D, MaxPooling2D , BatchNormalization ,Dropout ,Flatten , Dense , Input , Rescaling , Resizing
from keras.models import Sequential
from tensorflow.keras.preprocessing.image import ImageDataGenerator, array_to_img, img_to_array, load_img
from tensorflow.keras.applications import MobileNetV2
import warnings
with warnings.catch_warnings():
warnings.simplefilter("ignore")
plt.style.use('ggplot')
2024-11-02 17:37:23.776857: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:485] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered 2024-11-02 17:37:23.790738: E external/local_xla/xla/stream_executor/cuda/cuda_dnn.cc:8454] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered 2024-11-02 17:37:23.794846: E external/local_xla/xla/stream_executor/cuda/cuda_blas.cc:1452] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered 2024-11-02 17:37:23.805597: I tensorflow/core/platform/cpu_feature_guard.cc:210] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations. To enable the following instructions: AVX2 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags. 2024-11-02 17:37:25.240955: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Could not find TensorRT
1.2. Cargar los Datos¶
# dataDirList indicmaos la ubicacion de los datos.
dataDirList = ['/mnt/c/Users/edgardo/git-edgardo/Austral/TF-Diplo/DataSet/Dataset_splits/']
#selectedClasses definimos las clases específicas que el modelo deberá reconocer.
selectedClasses = ['cardboard', 'compost', 'glass', 'metal', 'paper', 'plastic', 'trash']
Recorremos los directorios de imágenes indicado por dataDirList, seleccionamos los archivos correspondientes a las clases definidas en selectedClasses, y almacenamos las rutas y etiquetas de cada imagen en un DataFrame "df" de pandas.
imgPaths = []
labels = []
for dataDir in dataDirList:
for className in os.listdir(dataDir):
if className in selectedClasses :
classPath = os.path.join(dataDir,className)
for img in os.listdir(classPath):
imgPath = os.path.join(classPath,img)
imgPaths.append(imgPath)
labels.append(className)
# Se convierte imgPaths y labels en un DataFrame df, con dos columnas: 'imgPath' para las rutas de las imágenes y 'label' para las etiquetas de clase.
df = pd.DataFrame({
'imgPath':imgPaths,
'label':labels
})
# Se reorganiza aleatoriamente df y se restablecen los índices
# sirve para:
# Evitar sesgo en el orden de las clases: Aleatorizar las filas asegura que todas las clases estén distribuidas al azar.
# Preparar para entrenamiento: permite obtener subconjuntos más representativos de las clases para cada uno de estos conjuntos.Train,Test,Validacion
# Consistencia de índices: se asegura que el DataFrame tenga un índice continuo, útil en análisis y modelos que dependen de un acceso estructurado y ordenado a las filas.
df = df.sample(frac=1).reset_index(drop=True) # Shuffle
df
| imgPath | label | |
|---|---|---|
| 0 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | paper |
| 1 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | cardboard |
| 2 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | compost |
| 3 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | metal |
| 4 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | compost |
| ... | ... | ... |
| 7112 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | cardboard |
| 7113 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | trash |
| 7114 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | paper |
| 7115 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | glass |
| 7116 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | paper |
7117 rows × 2 columns
Dividimos el conjunto de datos en dos subconjuntos (entrenamiento y prueba) siguiendo un criterio de clase específico. El objetivo es asegurar que ambos conjuntos tengan una distribución representativa de cada clase.
# obtemos la proporción de manera que el ratio de cada clase sea para entranamiento y el restante para pruebas
# ej. si ratio es 0.8 significa 80% para entranamiento y 20% para pruebas.
def DataFrameSpliting(df , ratio , classesList):
trainDf = pd.DataFrame(columns = ['imgPath','label'])
testDf = pd.DataFrame(columns = ['imgPath','label'])
for clas in classesList :
tempDf = df[df['label'] == clas]
lastIndex = int(len(tempDf) * ratio)
trainClassDf = tempDf[:lastIndex]
testClassDf = tempDf[lastIndex:]
trainDf = pd.concat([trainDf , trainClassDf] , axis=0)
testDf = pd.concat([testDf , testClassDf] , axis=0)
# Aleatorizamos (sample(frac=1)) y restablecemos los índices (reset_index(drop=True)) de ambos DataFrames, para que estén en orden sin sesgo de clase ni saltos en los índices.
return trainDf.sample(frac=1).reset_index(drop=True) , testDf.sample(frac=1).reset_index(drop=True) # shuffling , reset index
# Extraemos las clases únicas en la columna label del DataFrame df
classList = list(df['label'].unique())
# Dividimos el DataFrame en entrenamiento y prueba.
# trainDf: contendrá el 85% de los datos de cada clase. testDf: contendrá el 15% restante de cada clase.
trainDf , testDf = DataFrameSpliting(df , 0.85 , classList)
trainDf
| imgPath | label | |
|---|---|---|
| 0 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | metal |
| 1 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | compost |
| 2 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | trash |
| 3 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | plastic |
| 4 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | glass |
| ... | ... | ... |
| 6042 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | trash |
| 6043 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | trash |
| 6044 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | paper |
| 6045 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | cardboard |
| 6046 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | plastic |
6047 rows × 2 columns
Contamos la cantidad de ocurrencias de cada clase en la columna label del DataFrame trainDf.
trainDf['label'].value_counts()
label compost 929 paper 921 glass 921 trash 880 cardboard 861 plastic 823 metal 712 Name: count, dtype: int64
testDf
| imgPath | label | |
|---|---|---|
| 0 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | glass |
| 1 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | cardboard |
| 2 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | metal |
| 3 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | plastic |
| 4 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | cardboard |
| ... | ... | ... |
| 1065 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | compost |
| 1066 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | compost |
| 1067 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | paper |
| 1068 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | trash |
| 1069 | /mnt/c/Users/edgardo/git-edgardo/Austral/TF-Di... | plastic |
1070 rows × 2 columns
Contamos la cantidad de ocurrencias de cada clase en la columna label del DataFrame testDf.
testDf['label'].value_counts()
label compost 164 glass 163 paper 163 trash 156 cardboard 152 plastic 146 metal 126 Name: count, dtype: int64
Mostramos un conjunto de imágenes junto con sus etiquetas en una cuadrícula de 3x8.
imgPaths = df['imgPath']
fig, axs = plt.subplots(3, 8, figsize=(25, 10))
axs = axs.flatten()
for ax,imgPath in zip(axs , imgPaths):
label = str(imgPath).split('/')[-2] # extract label of an imgae from a path
img = cv2.imread(imgPath)
ax.imshow(img)
ax.set_title(label)
ax.axis('off')
plt.tight_layout()
plt.show()
Configuramos un generador de imágenes de entrenamiento, datagenTrain, usando la clase ImageDataGenerator de Keras. Este generador aplica transformaciones de aumento de datos (data augmentation) en imágenes para aumentar la diversidad del conjunto de datos sin agregar más imágenes reales.
datagenTrain = ImageDataGenerator(
rescale=1./255, # Escala los valores de píxeles de las imágenes al rango [0,1] dividiendo cada valor por 255.
# Esto es útil para normalizar los datos de imágenes, ya que los modelos suelen funcionar mejor con valores de entrada en este rango.
zoom_range=(1.0, 1.2), # Aplica un aumento aleatorio de zoom en cada imagen.
# En este caso, 1.0 significa sin zoom y 1.2 representa un zoom máximo del 120%
# Esto ayuda a que el modelo sea más robusto frente a imágenes de diferentes tamaños y proporciones.
horizontal_flip=True, # Realiza una inversión horizontal aleatoria de la imagen (de izquierda a derecha).
vertical_flip=True, # Realiza una inversión vertical aleatoria de la imagen (de arriba a abajo).
rotation_range=45, # Rota la imagen aleatoriamente hasta un máximo de 45 grados en cualquier dirección (en sentido horario o antihorario).
)
Preparamos los generadores de datos de entrenamiento y prueba (trainGenerator y testGenerator) para el modelo de clasificación de imágenes, utilizando los dataframes trainDf y testDf. Los generadores leerán las imágenes desde las rutas y aplicarán aumentos de datos al conjunto de entrenamiento y test.
# Define el tamaño objetivo de las imágenes para redimensionarlas uniformemente a (224, 224) píxeles.
# Nos aseguramos que todas las imágenes tengan el mismo tamaño cuando se alimenta al modelo.
IMG_SIZE = (224,224)
trainGenerator = datagenTrain.flow_from_dataframe(
trainDf , # dataset de entrenamiento
x_col='imgPath',
y_col='label',
target_size=IMG_SIZE, # Redimensiona las imágenes de prueba a (224, 224).
batch_size=64 , # Genera 64 imágenes del datagenTrain (invertidas, rotadas, con zoom, etc.) de una vez.
class_mode='categorical' # Clasifica las etiquetas en formato categórico (necesario para clasificación multiclase).
)
# Generador de datos de prueba, configurado con un aumento mínimo:
datagenTest = ImageDataGenerator( rescale=1./255 ) # Normaliza las imágenes escalándolas al rango [0, 1].
# No se aplican otras transformaciones (sin rotaciones ni volteos), lo cual es habitual para el conjunto de prueba.
# Generador de datos de prueba:
testGenerator = datagenTest.flow_from_dataframe(
testDf , # dataset de test
x_col='imgPath',
y_col='label',
target_size=IMG_SIZE, # Redimensiona las imágenes de prueba a (224, 224).
batch_size=8 , # Genera lotes de 8 imágenes.
class_mode='categorical', # Clasificación multiclase para el conjunto de prueba.
shuffle=False # No se mezcla el conjunto de prueba, preservando el orden original para comparaciones de resultados.
)
# Imprimimos el número total de muestras en los conjuntos de entrenamiento y prueba (trainGenerator.samples y testGenerator.samples).
# Vemos el tamaño de cada conjunto.
print(f"Training set size: {trainGenerator.samples}")
print(f"Testing set size: {testGenerator.samples}")
Found 6040 validated image filenames belonging to 7 classes.
/home/edgardo/miniconda3/envs/py310/lib/python3.10/site-packages/keras/src/legacy/preprocessing/image.py:920: UserWarning: Found 7 invalid image filename(s) in x_col="imgPath". These filename(s) will be ignored. warnings.warn(
Found 1070 validated image filenames belonging to 7 classes. Training set size: 6040 Testing set size: 1070
Creamos un modelo secuencial de Keras para clasificar imágenes en 7 categorías utilizando una versión preentrenada de la red MobileNetV2 como base. MobileNetV2 es un modelo de red neuronal convolucional (CNN) preentrenado que es eficiente y adecuado para dispositivos con recursos limitados. Es especialmente útil para tareas de clasificación de imágenes.
Model = Sequential([
MobileNetV2(weights='imagenet', include_top=False, input_shape=(224,224,3)),
# Utiliza pesos preentrenados de ImageNet, lo que significa que el modelo ya ha aprendido a identificar características generales de las
# imágenes durante su entrenamiento en ese conjunto de datos.
# include_top=False, significa que se omiten las capas de clasificación final del modelo original MobileNetV2,
# lo que permite personalizar la parte superior del modelo para una tarea específica (en este caso, clasificación de residuos).
# input_shape=(224,224,3) define la forma de entrada del modelo, se especifica que las imágenes de entrada deben ser de 224 píxeles de
# ancho por 224 píxeles de alto y tener 3 canales (RGB).
Flatten(), # Esta capa convierte la salida 2D de las capas convolucionales anteriores en un vector 1D. Esto es necesario para que la salida
# se pueda pasar a las capas densas (fully connected).
# Ignora las capas de flatten y dense cuando include_top = False
Dense(64, activation='relu'), # Añade una capa densa (o fully connected) con 64 neuronas. La función de activación ReLU (Rectified Linear Unit)
# se utiliza para introducir no linealidad, lo que ayuda al modelo a aprender relaciones complejas en los datos.
BatchNormalization(), # Normaliza las salidas de la capa anterior para mejorar la estabilidad y la velocidad del entrenamiento.
# Esto ayuda a mitigar problemas relacionados con la variación de covariables.
Dropout(0.08), # Añade una capa de Dropout, que apaga aleatoriamente el 8% de las neuronas durante el entrenamiento.
# Esto ayuda a prevenir el sobreajuste, lo que significa que el modelo se generaliza mejor a nuevos datos.
Dense(7, activation='softmax') # Esta es la capa de salida del modelo, que tiene 7 neuronas, cada una representando una de las categorías
# de clasificación (en este caso, 7 tipos de residuos).
# activation='softmax' se utiliza para que las salidas de las neuronas representen probabilidades.
])
WARNING: All log messages before absl::InitializeLog() is called are written to STDERR I0000 00:00:1730579920.624575 25294 cuda_executor.cc:1001] could not open file to read NUMA node: /sys/bus/pci/devices/0000:01:00.0/numa_node Your kernel may have been built without NUMA support. 2024-11-02 17:38:40.708561: W tensorflow/core/common_runtime/gpu/gpu_device.cc:2343] Cannot dlopen some GPU libraries. Please make sure the missing libraries mentioned above are installed properly if you would like to use GPU. Follow the guide at https://www.tensorflow.org/install/gpu for how to download and setup the required libraries for your platform. Skipping registering GPU devices...
Modificamos el modelo preentrenado que se ha creado previamente (denominado Model).
preTrainedModel = Model.layers[0] # Esta línea extrae la primera capa del modelo Model, que se supone que es el modelo preentrenado (en este caso, MobileNetV2). El modelo preentrenado se asigna a la variable preTrainedModel.
for layer in preTrainedModel.layers[:-4]: # "Congelamos todas las capas excepto las primeras y las últimas 3 capas; las haremos entrenables (los pesos cambian con el entrenamiento)."
layer.trainable = False # se establece que estas capas no son entrenables (es decir, sus pesos no se actualizarán durante el entrenamiento).
Configuramos el proceso de entrenamiento del modelo de aprendizaje profundo en Keras.
Model.compile(optimizer='adam',loss='categorical_crossentropy' ,metrics=['accuracy'])
# optimizer='adam' especifica el optimizador que se usará para actualizar los pesos del modelo durante el entrenamiento.
# Utilizamos el optimizador Adam, que es uno de los optimizadores más populares.
# Adam combina las ventajas de dos métodos: AdaGrad y RMSProp, y es conocido por su efectividad en una amplia variedad de tareas
# de aprendizaje automático.
# loss='categorical_crossentropy' define la función de pérdida que se utilizará para evaluar la calidad de las predicciones del modelo.
# categorical_crossentropy es adecuada para problemas de clasificación multiclase donde las etiquetas son categorías distintas.
# Esta función mide la disimilitud entre la distribución de probabilidad predicha y la verdadera distribución de probabilidad de las clases.
# Se usa comúnmente cuando las etiquetas son representadas como matrices de clase one-hot (es decir, un formato donde cada etiqueta se convierte
# en un vector que tiene 1 en la posición de la clase y 0 en otras).
# metrics=['accuracy'] especifica las métricas que se desean observar durante el entrenamiento y la evaluación del modelo.
# Utilizamos precisión (accuracy) como métrica, que mide la proporción de predicciones correctas sobre el total de predicciones realizadas.
Entrenamos el modelo previamente definido en Keras.¶
# entrenamos el modelo con los datos proporcionados. Este método devuelve un objeto History que contiene información sobre el entrenamiento,
# como las pérdidas y las métricas a lo largo de las épocas.
history = Model.fit(
# Este es el generador de datos que proporciona las imágenes de entrenamiento y sus etiquetas en lotes (batches) durante el entrenamiento.
trainGenerator,
# especifica un generador de datos para la validación, que se utilizará para evaluar el modelo después de cada época.
# En este caso, testGenerator proporciona las imágenes y etiquetas de prueba.
validation_data = testGenerator,
# Establece el número de épocas para el entrenamiento.
epochs=50,
# batch_size=64, # we define it above inside trainGenerator
# Controla la cantidad de información que se muestra durante el entrenamiento. verbose=1 significa que se imprimirá una barra de
# progreso en cada época, mostrando información como la pérdida y la precisión.
verbose=1,
# se especifica que utilice callbacks, que son funciones que se pueden ejecutar en puntos específicos durante el entrenamiento.
# EarlyStopping, que detiene el entrenamiento si la métrica monitorizada (en este caso, la precisión de validación val_accuracy)
# no mejora después de un número específico de épocas (definido por patience=4).
# Esto ayuda a evitar el sobreajuste (overfitting) al detener el entrenamiento cuando el modelo ya no mejora.
# restore_best_weights=True asegura que, al final del entrenamiento, el modelo restaurará los pesos de la mejor época, es decir,
# los pesos en los que se obtuvo la mejor precisión de validación, en lugar de usar los pesos del final del entrenamiento.
callbacks=[tf.keras.callbacks.EarlyStopping(
patience=4,
monitor='val_accuracy',
restore_best_weights=True)])
/home/edgardo/miniconda3/envs/py310/lib/python3.10/site-packages/keras/src/trainers/data_adapters/py_dataset_adapter.py:121: UserWarning: Your `PyDataset` class should call `super().__init__(**kwargs)` in its constructor. `**kwargs` can include `workers`, `use_multiprocessing`, `max_queue_size`. Do not pass these arguments to `fit()`, as they will be ignored. self._warn_if_super_not_called()
Epoch 1/50 95/95 ━━━━━━━━━━━━━━━━━━━━ 167s 2s/step - accuracy: 0.6896 - loss: 0.9393 - val_accuracy: 0.8290 - val_loss: 0.5396 Epoch 2/50 95/95 ━━━━━━━━━━━━━━━━━━━━ 165s 2s/step - accuracy: 0.8658 - loss: 0.3913 - val_accuracy: 0.8748 - val_loss: 0.3973 Epoch 3/50 95/95 ━━━━━━━━━━━━━━━━━━━━ 170s 2s/step - accuracy: 0.8962 - loss: 0.3050 - val_accuracy: 0.8776 - val_loss: 0.4191 Epoch 4/50 95/95 ━━━━━━━━━━━━━━━━━━━━ 175s 2s/step - accuracy: 0.9193 - loss: 0.2522 - val_accuracy: 0.8832 - val_loss: 0.3677 Epoch 5/50 95/95 ━━━━━━━━━━━━━━━━━━━━ 170s 2s/step - accuracy: 0.9289 - loss: 0.2141 - val_accuracy: 0.8757 - val_loss: 0.3856 Epoch 6/50 95/95 ━━━━━━━━━━━━━━━━━━━━ 159s 2s/step - accuracy: 0.9447 - loss: 0.1641 - val_accuracy: 0.8860 - val_loss: 0.3792 Epoch 7/50 95/95 ━━━━━━━━━━━━━━━━━━━━ 162s 2s/step - accuracy: 0.9514 - loss: 0.1462 - val_accuracy: 0.8925 - val_loss: 0.3920 Epoch 8/50 95/95 ━━━━━━━━━━━━━━━━━━━━ 160s 2s/step - accuracy: 0.9536 - loss: 0.1422 - val_accuracy: 0.8925 - val_loss: 0.3890 Epoch 9/50 95/95 ━━━━━━━━━━━━━━━━━━━━ 158s 2s/step - accuracy: 0.9629 - loss: 0.1192 - val_accuracy: 0.9047 - val_loss: 0.3665 Epoch 10/50 95/95 ━━━━━━━━━━━━━━━━━━━━ 160s 2s/step - accuracy: 0.9570 - loss: 0.1240 - val_accuracy: 0.8925 - val_loss: 0.4004 Epoch 11/50 95/95 ━━━━━━━━━━━━━━━━━━━━ 158s 2s/step - accuracy: 0.9653 - loss: 0.0997 - val_accuracy: 0.9150 - val_loss: 0.3810 Epoch 12/50 95/95 ━━━━━━━━━━━━━━━━━━━━ 158s 2s/step - accuracy: 0.9779 - loss: 0.0758 - val_accuracy: 0.9056 - val_loss: 0.3890 Epoch 13/50 95/95 ━━━━━━━━━━━━━━━━━━━━ 159s 2s/step - accuracy: 0.9663 - loss: 0.1027 - val_accuracy: 0.8953 - val_loss: 0.4588 Epoch 14/50 95/95 ━━━━━━━━━━━━━━━━━━━━ 159s 2s/step - accuracy: 0.9720 - loss: 0.0780 - val_accuracy: 0.9075 - val_loss: 0.4054 Epoch 15/50 95/95 ━━━━━━━━━━━━━━━━━━━━ 159s 2s/step - accuracy: 0.9776 - loss: 0.0728 - val_accuracy: 0.9159 - val_loss: 0.3994 Epoch 16/50 95/95 ━━━━━━━━━━━━━━━━━━━━ 159s 2s/step - accuracy: 0.9782 - loss: 0.0663 - val_accuracy: 0.9009 - val_loss: 0.4280 Epoch 17/50 95/95 ━━━━━━━━━━━━━━━━━━━━ 160s 2s/step - accuracy: 0.9787 - loss: 0.0623 - val_accuracy: 0.9065 - val_loss: 0.4386 Epoch 18/50 95/95 ━━━━━━━━━━━━━━━━━━━━ 158s 2s/step - accuracy: 0.9726 - loss: 0.0662 - val_accuracy: 0.9093 - val_loss: 0.4124 Epoch 19/50 95/95 ━━━━━━━━━━━━━━━━━━━━ 158s 2s/step - accuracy: 0.9775 - loss: 0.0640 - val_accuracy: 0.9131 - val_loss: 0.4369
Imprimimos una tabla que describe cada capa del modelo, incluyendo:
- Tipo de capa: Indica qué tipo de capa se está utilizando (por ejemplo, Conv2D, Dense, Flatten, etc.).
- Output Shape: Muestra la forma de la salida de cada capa. Esto es útil para entender cómo los datos están fluyendo a través de las distintas capas del modelo.
- Número de parámetros: Indica cuántos parámetros (pesos y sesgos) tiene cada capa. Esto incluye tanto los parámetros que se entrenan como aquellos que no se entrenan (por ejemplo, en capas congeladas).
Model.summary()
Model: "sequential_1"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓ ┃ Layer (type) ┃ Output Shape ┃ Param # ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩ │ mobilenetv2_1.00_224 │ (None, 7, 7, 1280) │ 2,257,984 │ │ (Functional) │ │ │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ flatten_1 (Flatten) │ (None, 62720) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dense_2 (Dense) │ (None, 64) │ 4,014,144 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ batch_normalization_1 │ (None, 64) │ 256 │ │ (BatchNormalization) │ │ │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dropout_1 (Dropout) │ (None, 64) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dense_3 (Dense) │ (None, 7) │ 455 │ └─────────────────────────────────┴────────────────────────┴───────────────┘
Total params: 15,127,895 (57.71 MB)
Trainable params: 4,427,527 (16.89 MB)
Non-trainable params: 1,845,312 (7.04 MB)
Optimizer params: 8,855,056 (33.78 MB)
Visualizamos la precisión del modelo a lo largo de las épocas durante el entrenamiento. Mostramos cómo la precisión del modelo en el conjunto de entrenamiento y en el conjunto de validación cambian a medida que avanza el entrenamiento. Esto nos permite evaluar el rendimiento del modelo y detectar problemas como el sobreajuste (cuando la precisión de entrenamiento sigue mejorando pero la precisión de validación comienza a disminuir).
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'validation'], loc='upper left')
plt.show()
Realizmos la predicción de las clases para el conjunto de datos de prueba utilizando el modelo de aprendizaje automático previamente entrenado.
predictions contendrá las probabilidades de cada clase para cada imagen en el conjunto de datos de prueba, lo que te permitirá determinar cuál es la clase predicha más probable para cada imagen.
predictions = Model.predict(testGenerator)
134/134 ━━━━━━━━━━━━━━━━━━━━ 18s 129ms/step
Vemos que indice tiene cada clase
trainGenerator.class_indices
{'cardboard': 0,
'compost': 1,
'glass': 2,
'metal': 3,
'paper': 4,
'plastic': 5,
'trash': 6}
Comparamos las predicciones del modelo con las clases verdaderas, lo que permite calcular métricas de rendimiento.
trueClasses = testGenerator.classes
trueClasses[:10]
[3, 6, 6, 2, 4, 4, 4, 2, 3, 5]
predictedClasses = predictions.argmax(axis=-1)
predictedClasses[:10]
array([3, 6, 6, 2, 5, 4, 4, 2, 3, 5])
Verificamos el rendimiento del modelo en datos que no ha visto antes (el conjunto de prueba).
test_loss, test_accuracy = Model.evaluate(testGenerator)
test_accuracy
134/134 ━━━━━━━━━━━━━━━━━━━━ 16s 120ms/step - accuracy: 0.8973 - loss: 0.4280
0.8981308341026306
Evaluamos el rendimiento del modelo de clasificación. Observamos cuántas predicciones son correctas, asi como en dónde se producen errores. Por ejemplo, puede mostrar si hay confusión entre ciertas clases, lo que puede ayudar a identificar áreas de mejora para el modelo.
CM = confusion_matrix(trueClasses, predictedClasses)
sns.heatmap(CM, center = True,cmap='terrain',annot=True ,fmt='.5g')
plt.show()
1. Clasificación General¶
- La diagonal de la matriz (valores en la diagonal principal) representa las clasificaciones correctas del modelo.
- Se puede ver que, en general, el modelo tiene un buen desempeño en la clasificación de la mayoría de las categorías, ya que los valores en la diagonal son relativamente altos en comparación con los errores de clasificación (valores fuera de la diagonal).
2. Detalles por Clase¶
Cardboard: De 152 ejemplos, se clasificaron 136 correctamente como 'cardboard', pero hubo 8 errores clasificados como 'trash'. Esto sugiere que el modelo puede confundir cartón con basura, posiblemente debido a características visuales similares.
Compost: De 164 ejemplos, se clasificaron 152 correctamente. Se registraron algunos errores (2 como 'glass' y 8 como 'trash'), lo que sugiere que la clasificación de compost puede ser problemática cuando se presentan elementos no reciclables.
Glass: La clase 'glass' muestra un buen rendimiento general, con 149 clasificaciones correctas de 162, aunque hay confusiones con 'plastic' (12 errores) y 'trash' (2 errores).
Metal: Para 'metal', de 125 ejemplos, 112 fueron clasificados correctamente. Sin embargo, hubo 5 clasificaciones erróneas como 'glass', lo que sugiere que el modelo podría confundir algunos objetos de metal con objetos de vidrio.
Paper: En 'paper', de 163 ejemplos, 149 se clasificaron correctamente. Hay confusiones menores, especialmente con 'trash' (4 errores).
Plastic: Para 'plastic', de 146 ejemplos, hubo 115 clasificaciones correctas, pero también 12 fueron erróneamente clasificadas como 'glass'. Esto indica que puede haber desafíos en distinguir ciertos tipos de plástico de vidrio.
Trash: Finalmente, 'trash' tuvo la mayor cantidad de ejemplos correctos (148 de 156), con pocos errores en otras clases. Sin embargo, se confunde ocasionalmente con 'cardboard', 'plastic' y 'paper'.
3. Observaciones Generales¶
Mejor Desempeño: La categoría 'trash' parece ser la mejor clasificada, con un alto número de aciertos y confusiones mínimas con otras clases.
Errores de Clasificación: Los errores son más comunes en las clases donde los residuos tienen características visuales que pueden confundirse entre sí, como 'glass' y 'plastic', o 'paper' y 'trash'.
Desempeño del Modelo: En general, el modelo tiene un desempeño razonable, pero puede mejorarse, especialmente en clases donde hay confusiones significativas. Se puede considerar el ajuste del modelo o la recolección de más datos de entrenamiento para esas clases problemáticas.
4. Recomendaciones¶
Reentrenamiento: Podriamos considerar realizar un reentrenamiento del modelo con un enfoque especial en las clases que presentan confusiones significativas. Esto puede incluir el uso de técnicas de aumentación de datos para diversificar las imágenes de esas clases.
Análisis Adicional: Podriamos realizar un análisis más detallado de las imágenes clasificadas incorrectamente para entender por qué el modelo se confunde entre ciertas clases. Esto podria proporcionar información útil para mejorar el rendimiento del modelo.
Evaluación Continua: Necesitariamos mantener un seguimiento continuo del rendimiento del modelo, especialmente si se introduce un nuevo conjunto de datos o se realizan cambios en las condiciones de recolección de datos.
Con estas observaciones y recomendaciones, podemos tomar acción para mejorar el rendimiento del modelo de clasificación de residuos y minimizar los errores de clasificación.
Volcamos la Matriz de confusion a un archivo
cm_df = pd.DataFrame(CM, index=trainGenerator.class_indices.keys(), columns=trainGenerator.class_indices.keys())
cm_df.to_csv('matriz_confusion.csv', index=True)
Generamos un informe de clasificación donde:
- trueClasses son las etiquetas verdaderas (o reales) de las clases para los datos de prueba.
- predictedClasses son las etiquetas predichas por el modelo.
la función classification_report genera un informe que resume varias métricas de rendimiento del modelo de clasificación, como la precisión, el recall y la puntuación F1 para cada clase.
ClassificationReport = classification_report(trueClasses, predictedClasses)
print('Classification Report is : \n', ClassificationReport )
Classification Report is :
precision recall f1-score support
0 0.94 0.89 0.92 152
1 0.98 0.93 0.95 164
2 0.89 0.91 0.90 163
3 0.91 0.89 0.90 126
4 0.95 0.91 0.93 163
5 0.84 0.79 0.81 146
6 0.80 0.95 0.87 156
accuracy 0.90 1070
macro avg 0.90 0.90 0.90 1070
weighted avg 0.90 0.90 0.90 1070
Realizamos predicciones del conjunto de datos de prueba utilizando eln modelo previamente entrenado, almacenamos las imágenes y sus etiquetas correspondientes (predichas y verdaderas), y preparamos los datos para su análisis posterior, como la visualización de resultados.
images = []
predictedClasses = []
trueClasses = []
class_labels = list(testGenerator.class_indices.keys())
for i in range(len(testGenerator)):
img_batch, true_labels_batch = next(testGenerator) # Get next batch
true_class_idx = np.argmax(true_labels_batch[0]) # Get the true class index
# Predict the class for the image
prediction = Model.predict(img_batch)
predicted_class_idx = np.argmax(prediction[0]) # Get the predicted class index
# Convert the class indices to class labels
predicted_class = class_labels[predicted_class_idx]
true_class = class_labels[true_class_idx]
images.append(np.squeeze(img_batch[0]))
predictedClasses.append(predicted_class)
trueClasses.append(true_class)
if i >= 24: # For example, only plot 24 images
break
1/1 ━━━━━━━━━━━━━━━━━━━━ 1s 921ms/step 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 126ms/step 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 127ms/step 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 130ms/step 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 129ms/step 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 143ms/step 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 136ms/step 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 128ms/step 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 123ms/step 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 122ms/step 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 124ms/step 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 123ms/step 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 119ms/step 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 126ms/step 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 117ms/step 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 118ms/step 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 125ms/step 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 144ms/step 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 136ms/step 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 118ms/step 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 121ms/step 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 128ms/step 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 139ms/step 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 144ms/step 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 139ms/step
Visualizamos las imágenes procesadas junto con sus etiquetas de clase predichas y verdaderas.
fig, axs = plt.subplots(3, 8, figsize=(25, 10))
axs = axs.flatten()
for ax,img , pred , true in zip(axs , images , predictedClasses , trueClasses):
ax.imshow(img)
ax.set_title(f"Pred: {pred}, True: {true}" ,fontsize=14)
ax.axis('off')
plt.tight_layout()
plt.show()
Model.save('/mnt/c/Users/edgardo/git-edgardo/Austral/TF-Diplo/Tensorflow2/model_NoHayPlanetaB_tf2.keras')
Model.save_weights('/mnt/c/Users/edgardo/git-edgardo/Austral/TF-Diplo/Tensorflow2/model_NoHayPlanetaB.weights.h5')